﻿using System;
using System.Linq;
using System.ComponentModel;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.IO.IsolatedStorage;
using System.Runtime.Serialization;
using System.Security.Cryptography;
using System.Text;

namespace SBPweb.RIA.Client.Contracts.Base
{

    [CollectionDataContract]
    public abstract class LocalStoreBase<TBag, TFormatter> : ExtendableBag
        where TBag : ExtendableBag
        where TFormatter : LocalStoreFormatterBase
    {

        #region Fields

        private Collection<string> ignorableKeys = new Collection<string>();
        private static SHA256 sha = new SHA256Managed();
        private static Encoding enc = Encoding.UTF8;

        #endregion

        #region Constructor logic

        public LocalStoreBase()
            : base()
        {
        }

        public LocalStoreBase(IEnumerable<KeyValuePair<string, object>> keyValues)
            : base(keyValues)
        {
        }

        #endregion

        #region Property accessors

        protected static TFormatter Formatter
        {
            get;
            set;
        }

        protected static string FileName
        {
            get;
            set;
        }

        protected static StorageScopes Scope
        {
            get;
            set;
        }

        #endregion

        #region Methods

        public static TBag Load()
        {
            return Load(null);
        }

        public static TBag Load(LocalStoreFormatterBase formatter)
        {
            if (Formatter == null)
            {
                Formatter = Activator.CreateInstance<TFormatter>();
            }

            TBag settings = Activator.CreateInstance<TBag>();

            using (IsolatedStorageFile store = GetStoreBasedOnScope())
            {
                if (!store.FileExists(FileName))
                {
                    return settings;
                }

                using (var isoStream = store.OpenFile(FileName, FileMode.Open))
                {
                    try
                    {
                        DataContractSerializer s = new DataContractSerializer(typeof(TBag));
                        return (TBag)s.ReadObject(Formatter.DecodeIsoStream(isoStream));
                    }
                    catch (Exception e)
                    {
                        return settings;
                    }
                }
            }
        }

        public void Save()
        {
            using (IsolatedStorageFile store = GetStoreBasedOnScope())
            {
                using (IsolatedStorageFileStream isoStream = store.OpenFile(FileName, FileMode.Create))
                {
                    DataContractSerializer s = new DataContractSerializer(typeof(TBag));
                    TBag settings = (TBag)Activator.CreateInstance(typeof(TBag), this.Where(x => !ignorableKeys.Contains(x.Key)));
                    s.WriteObject(Formatter.EncodeIsoStream(isoStream), settings);
                }
            }
        }

        public void IgnoreDuringPersistency(string key)
        {
            ignorableKeys.Add(key);
        }

        public bool ContainsStream(string relativePath)
        {
            using (IsolatedStorageFile store = GetStoreBasedOnScope())
            {
                return store.FileExists(GetRelativeStorePathForUri(relativePath));
            }
        }

        public DateTimeOffset GetStreamLastWriteTime(string relativePath)
        {
            using (IsolatedStorageFile store = GetStoreBasedOnScope())
            {
                return store.GetLastWriteTime(GetRelativeStorePathForUri(relativePath));
            }
        }

        public Stream GetStreamReadOnly(string relativePath)
        {
            return GetStream(relativePath, FileMode.Open, FileAccess.Read);
        }

        public Stream GetStream(string relativePath, FileMode mode, FileAccess access)
        {
            using (IsolatedStorageFile store = GetStoreBasedOnScope())
            {
                string path = GetRelativeStorePathForUri(relativePath);
                if (!store.FileExists(path))
                {
                    return null;
                }

                return store.OpenFile(path, mode, access);
            }
        }

        protected static IsolatedStorageFile GetStoreBasedOnScope()
        {
            switch (Scope)
            {
                default:
                case StorageScopes.Application:
                    return IsolatedStorageFile.GetUserStoreForApplication();
                case StorageScopes.Site:
                    return IsolatedStorageFile.GetUserStoreForSite();
            }
        }

        public void PutStream(string relativePath, Stream stream)
        {
            using (IsolatedStorageFile store = GetStoreBasedOnScope())
            {
                byte[] bytes;
                int l = 0;
                using (BinaryReader reader = new BinaryReader(stream))
                {
                    l = (int)stream.Length;
                    bytes = new byte[l];
                    reader.Read(bytes, 0, l);
                    reader.Close();
                }
                if (store.AvailableFreeSpace < l)
                {
                    store.IncreaseQuotaTo(store.Quota + l * 2);
                }

                string path = GetRelativeStorePathForUri(relativePath);
                IsolatedStorageFileStream isoStream;
                if (!store.FileExists(path))
                {
                    MakeSureFolderStructureIsCreated(store, path);
                    isoStream = store.CreateFile(path);
                }
                else
                {
                    isoStream = store.OpenFile(path, FileMode.Open, FileAccess.Write);
                }
                try
                {
                    using (isoStream)
                    {
                        BinaryWriter writer = new BinaryWriter(isoStream);
                        writer.Write(bytes);
                        writer.Flush();
                        isoStream.Flush();
                        isoStream.Close();
                    }
                }
                catch
                {
                }
            }
        }

        public static string GetRelativeStorePathForUri(string uri)
        {
            byte[] hash = sha.ComputeHash(enc.GetBytes(uri));
            StringBuilder sb = new StringBuilder("ClientCache\\");
            foreach (byte b in hash)
            {
                sb.Append(b.ToString("x2").ToLower());
            }
            return sb.ToString();
        }

        private void MakeSureFolderStructureIsCreated(IsolatedStorageFile store, string path)
        {
            string dir = path.Substring(0, path.LastIndexOf('\\'));
            if (store.DirectoryExists(dir))
            {
                return;
            }
            store.CreateDirectory(dir);
        }

        #endregion

        #region Subclasses

        public enum StorageScopes
        {
            Application,
            Site
        }

        #endregion

    }
    
    public abstract class LocalStoreFormatterBase
    {

        public virtual IsolatedStorageFileStream DecodeIsoStream(IsolatedStorageFileStream isoStream)
        {
            return isoStream;
        }

        public virtual IsolatedStorageFileStream EncodeIsoStream(IsolatedStorageFileStream isoStream)
        {
            return isoStream;
        }

    }

}